package repositories

import (
	"context"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/zmqge/vireo-gin-admin/app/admin/models"
	"github.com/zmqge/vireo-gin-admin/config"
	"github.com/zmqge/vireo-gin-admin/pkg/scopes"

	"gorm.io/gorm"
)

// FileRepository 文件存储接口
type FileRepository interface {
	GetFileByID(ctx *gin.Context, id uint) (*models.FileModel, error)
	ListFiles(ctx *gin.Context) ([]*models.FileModel, error)
	CreateFile(ctx *gin.Context, entity *models.FileModel) error
	UpdateFile(ctx *gin.Context, entity *models.FileModel) error
	DeleteFile(ctx *gin.Context, id uint) error
	PageFiles(ctx *gin.Context, keywords string, pageNum, pageSize int) ([]*models.FileDto, int64, error)
	SaveToStorage(ctx context.Context, file *models.FileModel, reader io.Reader) (string, error)
	GetFileByMD5(ctx context.Context, md5Hash string, storageType string) (*models.FileModel, error)
	GetFileBySHA256(ctx context.Context, sha256Hash string, storageType string) (*models.FileModel, error)
}

// FileRepositoryImpl 文件存储实现
type FileRepositoryImpl struct {
	db *gorm.DB
}

// NewFileRepository 创建文件存储实例
func NewFileRepository(db *gorm.DB) FileRepository {
	return &FileRepositoryImpl{
		db: db,
	}
}

// GetFileByID 根据ID获取文件
func (r *FileRepositoryImpl) GetFileByID(ctx *gin.Context, id uint) (*models.FileModel, error) {
	var entity models.FileModel
	if err := r.db.Scopes(scopes.DataPermissionScope(ctx)).
		First(&entity, id).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, err
	}
	return &entity, nil
}

// ListFiles 获取文件列表
func (r *FileRepositoryImpl) ListFiles(ctx *gin.Context) ([]*models.FileModel, error) {
	var entities []*models.FileModel
	if err := r.db.Scopes(scopes.DataPermissionScope(ctx)).
		Find(&entities).Error; err != nil {
		return nil, err
	}
	return entities, nil
}

// CreateFile 创建文件记录并存储文件
func (r *FileRepositoryImpl) CreateFile(ctx *gin.Context, entity *models.FileModel) error {
	result := r.db.WithContext(context.WithValue(ctx.Request.Context(), "ginContext", ctx)).
		Create(entity)
	if result.Error != nil {
		return result.Error
	}
	return nil
}

// SaveToStorage 保存文件到存储系统
// 获取按配置组织的存储路径
func getConfiguredPath(basePath string) string {
	now := time.Now()
	format := config.App.FileUpload.PathFormat
	if format == "" {
		format = "2006/01/02" // 默认格式: 年/月/日
	}
	// filepath.Join会自动处理不同操作系统的路径分隔符
	return filepath.Join(basePath, now.Format(format))
}

// 将路径转换为Unix风格（统一使用正斜杠）
func toUnixPath(path string) string {
	return strings.ReplaceAll(path, "\\", "/")
}

// 生成文件名
func generateFileName(file *models.FileModel) string {
	strategy := config.App.FileUpload.NameStrategy
	ext := filepath.Ext(file.Name)

	switch strategy {
	case "timestamp":
		return fmt.Sprintf("%d%s", time.Now().UnixNano(), ext)
	case "uuid":
		return fmt.Sprintf("%s%s", uuid.New().String(), ext)
	case "md5":
		return fmt.Sprintf("%s%s", file.Md5, ext)
	case "sha256":
		return fmt.Sprintf("%s%s", file.Sha256, ext)
	default: // 默认使用MD5
		return fmt.Sprintf("%s%s", file.Md5, ext)
	}
}

// 构建文件存储路径
func buildFileStoragePath(basePath string, file *models.FileModel) (string, string, error) {
	// 创建按配置组织的子目录
	datePath := getConfiguredPath(basePath)

	// 确保目录存在
	if err := os.MkdirAll(datePath, 0755); err != nil {
		return "", "", fmt.Errorf("创建本地目录失败: %w", err)
	}

	// 使用配置策略生成文件名
	fileName := generateFileName(file)
	savePath := filepath.Join(datePath, fileName)
	relativePath := filepath.Join(filepath.Base(datePath), fileName)

	return toUnixPath(savePath), toUnixPath(relativePath), nil
}

// 本地存储实现
func (r *FileRepositoryImpl) SaveToStorage(ctx context.Context, file *models.FileModel, reader io.Reader) (string, error) {
	// 1. 确定存储路径 - 项目根目录下的storage文件夹
	basePath := filepath.Join("./storage/", config.App.FileUpload.BasePath)

	// 2. 构建文件存储路径
	savePath, relativePath, err := buildFileStoragePath(basePath, file)
	if err != nil {
		return "", err
	}
	file.SavePath = relativePath

	// 4. 创建本地文件
	// 在创建文件前添加路径验证
	if stat, err := os.Stat(savePath); err == nil && stat.IsDir() {
		return "", fmt.Errorf("路径是目录不是文件: %s", savePath)
	}
	outFile, err := os.Create(savePath)
	if err != nil {
		return "", fmt.Errorf("创建本地文件失败: %w", err)
	}
	defer outFile.Close()

	// 5. 复制文件内容
	if _, err := io.Copy(outFile, reader); err != nil {
		return "", fmt.Errorf("写入本地文件失败: %w", err)
	}

	// 6. 返回相对路径(相对于storage文件夹)或完整路径
	// 这里返回相对于storage文件夹的路径，便于统一管理
	relativePath, err = filepath.Rel(basePath, savePath)
	if err != nil {
		return "", fmt.Errorf("计算相对路径失败: %w", err)
	}

	return relativePath, nil
}

// UpdateFile 更新文件信息
func (r *FileRepositoryImpl) UpdateFile(ctx *gin.Context, entity *models.FileModel) error {
	result := r.db.Scopes(scopes.DataPermissionScope(ctx)).
		Save(entity)
	if result.Error != nil {
		return result.Error
	}
	if result.RowsAffected == 0 {
		return errors.New("文件未找到")
	}
	return nil
}

// DeleteFile 删除文件
func (r *FileRepositoryImpl) DeleteFile(ctx *gin.Context, id uint) error {
	// 1. 查询文件信息
	file, err := r.GetFileByID(ctx, id)
	if err != nil {
		return err
	}
	if file == nil {
		return errors.New("文件未找到")
	}

	// 3. 从数据库删除记录
	result := r.db.Scopes(scopes.DataPermissionScope(ctx)).
		Delete(&models.FileModel{}, id)
	if result.Error != nil {
		return result.Error
	}
	if result.RowsAffected == 0 {
		return errors.New("文件未找到")
	}
	return nil
}

// PageFiles 分页获取文件列表
func (r *FileRepositoryImpl) PageFiles(ctx *gin.Context, keywords string, pageNum, pageSize int) ([]*models.FileDto, int64, error) {
	var entities []*models.FileDto
	var total int64
	// 构建查询条件
	query := r.db.Scopes(scopes.DataPermissionScope(ctx)).
		Model(&models.FileModel{})
	if keywords != "" {
		query = query.Where("name LIKE ?", "%"+keywords+"%")
	}
	// 计算总数
	if err := query.Count(&total).Error; err != nil {
		return nil, 0, err
	}
	// 分页查询
	if err := query.Offset((pageNum - 1) * pageSize).
		Limit(pageSize).
		Find(&entities).Error; err != nil {
		return nil, 0, err
	}
	return entities, total, nil
}

// GetFileByMD5 根据MD5获取文件
func (r *FileRepositoryImpl) GetFileByMD5(ctx context.Context, md5Hash string, storageType string) (*models.FileModel, error) {
	var file models.FileModel
	err := r.db.WithContext(ctx).Where("md5 = ? AND storage_type = ?", md5Hash, storageType).First(&file).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil // 文件不存在
		}
		return nil, err
	}
	return &file, nil
}

// GetFileBySHA256 根据SHA256获取文件
func (r *FileRepositoryImpl) GetFileBySHA256(ctx context.Context, sha256Hash string, storageType string) (*models.FileModel, error) {
	var file models.FileModel
	err := r.db.WithContext(ctx).Where("sha256 = ? AND storage_type = ?", sha256Hash, storageType).First(&file).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil // 文件不存在
		}
		return nil, err
	}
	return &file, nil
}
